import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_small_project/constants/index.dart';
import 'package:flutter_small_project/utils/eventBus.dart';
import 'package:flutter_small_project/utils/toast.dart';

import 'tokenManager.dart';

class RequestDio {
  // 创建dio实例
  final Dio _dio = Dio();
  // 在构造函数里面配置请求的基本信息
  RequestDio() {
    // 配置请求的基地址
    _dio.options.baseUrl = GlobalVariable.BASE_URL;
    // 配置请求的连接时间
    _dio.options.connectTimeout =
        Duration(milliseconds: GlobalVariable.TIMEOUT);
    // 配置请求的接收值时间
    _dio.options.receiveTimeout =
        Duration(milliseconds: GlobalVariable.TIMEOUT);

    // 配置拦截器(请求 +  响应)
    _requestInterceptors();
  }
  // 封装 get请求
  Future get(String url, {Map<String, dynamic>? params}) async {
    final res = await _dio.get(url, queryParameters: params);
    final result = _requestResult(res);
    return result;
  }

  // 封装 post请求
  Future post(String url, {Map<String, dynamic>? data}) async {
    final res = await _dio.post(url, data: data);
    final result = _requestResult(res);
    return result;
  }

  // 封装 upload请求(适用于上传(Format)图片之类的为件)
    Future upload(String url, {FormData? data}) async {
    final res = await _dio.post(url, data: data);
    final result = _requestResult(res);
    return result;
  }

  // 封装  put请求
  Future put(String url, {Map<String, dynamic>? data}) async {
    final res = await _dio.put(url, data: data);
    final result = _requestResult(res);
    return result;
  }

  // 封装 delete请求
  Future delete(String url, {Map<String, dynamic>? data}) async {
    final res = await _dio.delete(url, data: data);
    final result = _requestResult(res);
    return result;
  }

  // 统一处理请求结果
  _requestResult(Response<dynamic> response) {
    if (response.data['code'] == GlobalVariable.SUCCESS_CODE) {
      return response.data['data'];
    }
    // 抛出异常(程序员 +用户)
    PromptAction.showError(response.statusMessage!);
    throw Exception(response.data['message']);
  }

  // 状态码为401时,判断refreshToken是否有效
  Future<bool> _isRefreshTokenExpired() async {
    // 获取refreshToken
    final String refreshToken = tokenManager.getRefreshToken();
    //1.创建一个dio实例对象
    Dio dio = Dio();
    // 为什么需要使用trycatch,那是因为如果请求失败,会抛出异常,不会继续往下执行,res此时是没有值的,所以需要使用trycatch
    try {
      //2.拼接请求地址,以及配置请求头信息
      final res =
          await dio.post('${GlobalVariable.BASE_URL}${HTTP_PATH.REFRESH_PATH}',
              options: Options(headers: {
                "Authorization": 'Bearer $refreshToken',
              }));
      if (res.data['code'] == 10000) {
        // 此时表示请求成功
        // 重新存储token和refreshToken
        tokenManager.setToken(res.data['data']['token'],
            refreshToken: res.data['data']['refreshToken']);
        return true;
      } else {
        // 此时表示请求失败
        return false;
      }
    } catch (e) {
      return false;
    }
  }

  // 拦截器方法
  _requestInterceptors() {
    _dio.interceptors.add(InterceptorsWrapper(
        // 请求拦截器
        onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
      // 在请求拦截器中统一携带Token
      //首先获取token
      final token = tokenManager.getToken();
      if (token.isNotEmpty) {
        options.headers['Authorization'] = 'Bearer $token';
      }
      return handler.next(options);
    },
        // 响应拦截器
        onResponse: (Response response, ResponseInterceptorHandler handler) {
      // 如果响应状态码 为 200 ~ 300,表示请求成功
      if (response.statusCode! >= 200 && response.statusCode! <= 300) {
        // 提示用户
        // PromptAction.showSuccess(response.statusMessage!);
        return handler.next(response);
      } else {
        // 响应状态码为 400 ~ 500,表示请求失败
        PromptAction.showError(response.statusMessage!);
        // 返回一个错误的Future
        return handler.reject(DioException(
          requestOptions: response.requestOptions,
        ));
      }
    },
        // 异常处理
        onError: (DioException error, ErrorInterceptorHandler handler) async {
      // 如果异常为401,表示Token过期,需要重新登录
      if (error.response?.statusCode == 401) {
        //[需求]: 需要在状态码为401的情况下,判断refreshToken是否有效
        //[步骤]:
        // 1. 获取refreshToken
        // 2. 判断refreshToken是否为空,
        // 2.1 如果不为空,就使用refreshToken发送请求获取最新的token和refreshToken,
        // 2.2 如果为空,就可以直接先删除缓存的token和refreshToken,然后跳转到登录页面
        final String refreshToken = tokenManager.getRefreshToken();
        if (refreshToken.isNotEmpty) {
          // 此refreshToken不为空,
          //我们还需要判断 refreshToken是否过期
          //我们可以在封装一个方法,返回一个bool值,如果为true,则表示没有过期,如果为false,则表示过期
          // 判断是否我们可以使用refreshToken发送请求,如果请求成功则表示没有过期,如果请求失败则表示过期
          //具体的步骤如下
          //[需求]:使用 refreshToken发送请求获取最新的token和refreshToken
          //[步骤]:可以封装一个方法_isRefreshTokenExpired方法
          //1. 首先重新创建一个dio实例对象,这是因为需要重新配置请求头信息
          //2. 拼接请求地址,以及配置请求头信息
          //3. 发送请求
          final flag = await _isRefreshTokenExpired();
          if (flag) {
            // 个人觉得因该是需要订阅一个事件,通知我的页面进行重新获取数据
            // 通过事件总线进行通知,但是有一定的延迟
            eventBus.fire(LoginEvent());
            // 当我们获取到最新的token之后,需要将之前失败的请求重新发送
            // [思路]:1.首先通过error对象里面的requestOptions属性获取之前的请求失败的信息
            //        2.然后通过_dio实例对象的fetch()方法重新发送请求
            final res = await _dio.fetch(error.requestOptions);
            // 返回响应的结果
            return handler.resolve(res);
          } else {
            // 清除缓存的token和refreshToken
            tokenManager.deleteToken();
            // 跳转到登录页面
            eventBus.fire(LoyoutEvent());
          }
        } else {
          // 此refreshToken为空,直接删除缓存的token和refreshToken,然后跳转到登录页面
          tokenManager.deleteToken();
          // 发布一个事件/消息
          eventBus.fire(LoyoutEvent());
          //  其他可以跳转的位置进行订阅
          // 有些东西一进页面就立马执行,所以需要放到一打开应用就执行的页面中(tabs_page)
        }
      }

      //之前常规的思路:
      // 首先是清除本地缓存中的Token
      // 然后跳转到登录页面
      //[问题]:在非页面中,是没有context的,所以无法使用Navigator.pushNamed(context, '/login');
      // [解决]:需要使用事件总线的方式来解决
      //[思路]: 1.下载  event_bus 包
      //        2.创建一个工具类,在工具类中先导入包创建一个event_bus对象
      //        3.发布一个事件 -->fire();
      //        4.在需要最早加载的页面中,订阅这个事件,并在订阅中执行跳转操作-->on().listen();
      return handler.next(error);
    }));
  }
}

final requestDio = RequestDio();
